Bug 16003 (CVE-2026-3012) - [SECURITY] CVE-2026-3012 group policy certificate enrollment uses http:// without validation
Summary: [SECURITY] CVE-2026-3012 group policy certificate enrollment uses http:// wit...
Status: RESOLVED FIXED
Alias: CVE-2026-3012
Product: Samba 4.1 and newer
Classification: Unclassified
Component: Tools (show other bugs)
Version: 4.24.0rc*
Hardware: All All
: P5 normal (vote)
Target Milestone: ---
Assignee: Douglas Bagnall
QA Contact: Samba QA Contact
URL:
Keywords:
Depends on:
Blocks: 16018
  Show dependency treegraph
 
Reported: 2026-02-17 23:48 UTC by Douglas Bagnall
Modified: 2026-05-26 14:12 UTC (History)
5 users (show)

See Also:


Attachments
basic patch (5.02 KB, patch)
2026-02-25 04:31 UTC, Douglas Bagnall
no flags Details
patch for 4.24 (18.58 KB, patch)
2026-02-27 03:49 UTC, Douglas Bagnall
no flags Details
patch for 4.23 (18.58 KB, patch)
2026-02-27 03:50 UTC, Douglas Bagnall
no flags Details
patch for 4.22 (18.58 KB, patch)
2026-02-27 03:51 UTC, Douglas Bagnall
no flags Details
patch for 4.21 (18.58 KB, patch)
2026-02-27 03:54 UTC, Douglas Bagnall
no flags Details
draft advisory (2.83 KB, patch)
2026-02-27 04:32 UTC, Douglas Bagnall
no flags Details
advisory v2 (2.97 KB, text/plain)
2026-02-27 09:56 UTC, Douglas Bagnall
dmulder: review+
Details
patch for 4.24, 4.23, 4.22, 4.21 v2 (18.60 KB, patch)
2026-03-10 22:58 UTC, Douglas Bagnall
dbagnall: review? (dmulder)
jsutton: review+
dmulder: review+
Details
advisory v3 (3.07 KB, text/plain)
2026-05-21 16:16 UTC, Stefan Metzmacher
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Douglas Bagnall 2026-02-17 23:48:43 UTC
Arad Inbar from DREAM Security Research Team writes (somewhat abbreviated):

The gp_cert_auto_enroll_ext.py component fetches the CA certificate over plain HTTP via SCEP (GetCACert) and installs it into the system-wide trust store without validating it against the trusted cACertificate already obtained from Kerberos-authenticated LDAP.

A machine-in-the-middle (MITM) attacker on the same network can inject a rogue CA certificate, thereby compromising all future TLS connections from the affected machine.

## Trigger Conditions

The vulnerable code path executes during:

  * First-time certificate enrollment after a machine joins the domain (most common scenario)

  * When the CA configuration changes in Active Directory (CA added, removed, or modified)

  * When the Samba GP cache is cleared (e.g., samba-gpupdate --unapply, Samba reinstall/upgrade)

  * When Group Policy is applied with --force and the cache state is ENFORCE


Once the enrollment has been cached, subsequent samba-gpupdate cycles (~90 minutes) skip the HTTP fetch. However, the first-time enrollment during domain join is the primary attack window and is the most operationally significant -- every new Linux domain member passes through this path exactly once, and the resulting trust store compromise is persistent.


## Root Cause

 1. fetch_certification_authorities() (line 138) queries LDAP and returns ca['cACertificate'] - the legitimate CA certificate delivered over Kerberos-authenticated LDAP.
 2. cert_enroll() (line 269) builds a hardcoded HTTP URL: url = 'http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?' % ca['hostname']
 3. getca() (line 204) fetches the certificate using requests.get() over plain HTTP.
 4. When the HTTP response contains Content-Type: application/x-x509-ca-cert, the response body is parsed and written directly to disk (lines 225–234) without comparison to ca['cACertificate'] retrieved from LDAP.
 5. The certificate is then symlinked into /usr/local/share/ca-certificates/, and update-ca-certificates is executed (lines 275–301), making the rogue CA system-trusted.
 6. The LDAP-provided certificate is only used as a fallback when the HTTP request fails (lines 209–223). If the attacker ensures the HTTP request succeeds, the LDAP certificate is never validated or compared.

## impact

• CVSS: AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N (~8.0 – High)
• CWE-295 (Improper Certificate Validation)
• Code executes as root (Computer policy context)
• Rogue CA persists across reboots
• All TLS connections from the machine can be intercepted

## Suggested fix

In getca(), after parsing the HTTP response, validate the SCEP-fetched certificate against the LDAP-provided certificate:

ldap_der = base64.b64decode(ca['cACertificate'])
ldap_cert = load_der_x509_certificate(ldap_der)

if cert.fingerprint(hashes.SHA256()) != ldap_cert.fingerprint(hashes.SHA256()):
    log.error('SCEP CA cert does not match LDAP cert -- possible MITM')
    return []


## Credit Request

We kindly request that the following researchers be credited for this discovery:

• Arad Inbar , DREAM Security Research Team
• Nir Somech , DREAM Security Research Team
• Ben Grinberg , DREAM Security Research Team
Comment 1 Douglas Bagnall 2026-02-18 02:50:15 UTC
Probably not changed by commit d3e0eec03cd93dcceaec7328ba8252bfa78f968e ("gpo: Remove sscep depends from Cert Auto Enroll") because I don't see checking being done with sscep. So I guess this goes back to 9c0a174af2007476cbff859f962a2667bc5004bf ("gpo: Add Certificate Auto Enrollment Policy", June 2021).
Comment 2 Douglas Bagnall 2026-02-18 03:08:52 UTC
Notes:

This affects machine account clients (domain members) using samba-gpupdate. 

Although the extension claims to need certmonger and cepces to work, it looks like it checks for these after downloading and linking the certs.

I guess it can't reliably use https:// because the cert might be needed for the TLS.

I can't work out why, if the GSS-encrypted ldap caCertificate attribute contains the same certificate as the http one, we bother with the http one at all.

The code references MS-CAESO 4.4.5.3.1.2 which is only available as an abandoned draft. It seems to indicate initialization using the certificates from LDAP alone.

Requiring that the ldap and http certificates match reduces the impact from trust takeover to a DoS.
Comment 3 David Mulder 2026-02-18 16:41:03 UTC
(In reply to Douglas Bagnall from comment #2)

MS-CAESO was only ever in draft, but Windows clients implement it. For some reason MS never finished the document.
Comment 4 David Mulder 2026-02-18 16:41:53 UTC
(In reply to David Mulder from comment #3)

I argued with MS docs at SambaXP one year, but MS refuses to finish the document for some reason.
Comment 5 David Mulder 2026-02-18 16:43:08 UTC
Either way, I don't recall why that http fetch was in there to begin with.
Comment 6 David Mulder 2026-02-18 16:52:11 UTC
Seems to me we should drop that http lookup altogether and simply use the cACertificate retrieved via ldap.
Comment 7 Douglas Bagnall 2026-02-18 19:37:29 UTC
(In reply to David Mulder from comment #6)
> Seems to me we should drop that http lookup altogether and simply use the cACertificate retrieved via ldap.

Yeah, I think this is the way to go.
Comment 8 Douglas Bagnall 2026-02-18 20:59:34 UTC
As far as I can tell, this only works *as a feature* for Samba members using group policy in a Windows domain which has Network Devices Enrollment Services enabled (how common is NDES?).

But as an *attack*, it affects any machine that runs samba-gpupdate as root and which has certain values in the "Software\Policies\Microsoft\Cryptography\AutoEnrollment" registry. My guess it is unlikely for machines to have that accidentally, though there seems to have been distro interest in making it work so who knows. Probably to be vulnerable, you have to have at least thought you were using it for NDES.

If the machine does not have the right GPO registry values, enrollment is not attempted, the http:// is not fetched, no certs are added.

David, does this seem correct?
Comment 9 David Mulder 2026-02-18 21:01:36 UTC
> David, does this seem correct?
(In reply to Douglas Bagnall from comment #8)

Yes, that's correct.
Comment 10 Douglas Bagnall 2026-02-19 21:22:22 UTC
(In reply to Douglas Bagnall from comment #8)
> If the machine does not have the right GPO registry values, enrollment is not attempted, the http:// is not fetched, no certs are added.

Next question is, how do machines get these registry values? 

As far as I can tell we add the values in tests but have no tooling to do that.

Is it added via the Windows GPME? Are there other ways to do it?
Comment 11 arad 2026-02-21 07:10:44 UTC
The registry values under HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Cryptography\AutoEnrollment are pushed to domain members through standard Group Policy, configured by the domain administrator in the Group Policy Management Editor (GPME):
Computer Configuration > Policies > Windows Settings > Security Settings > Public Key Policies > Certificate Services Client - Auto-Enrollment


When the admin sets this to Enabled and checks both "Renew expired certificates" and "Update certificates that use certificate templates", the GPO writes AEPolicy = 7 to the registry on all domain members during the next gpupdate cycle.

This is the standard and documented way to deploy AD CS certificate auto-enrollment across a domain it's not an obscure or manual configuration. Any organization using Active Directory Certificate Services (AD CS) with auto-enrollment will have this GPO applied domain-wide, affecting every domain-joined machine including Linux members running samba-gpupdate.
Comment 12 Douglas Bagnall 2026-02-25 04:31:25 UTC
Created attachment 18866 [details]
basic patch

(In reply to arad from comment #11)
> This is the standard and documented way to deploy AD CS certificate auto-enrollment across a domain it's not an obscure or manual configuration.

Yes. I was also interested in finding out whether there was any way pure Samba domains could be affected, but it seems not (no NDES for a start).

CVSS3.1 8.0 (AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N) seems about right (as Arad suggests).

The attached patch just removes the http request, doing only what we did in fallback situation (`git diff -b` to see this clearly).

Currently it causes test failures.

This affects all versions since 4.16 (2022).
Comment 13 Douglas Bagnall 2026-02-25 04:33:52 UTC
I am still interested in how common NDES is compared to having the certs in LDAP. In other words, will this break things for people?
Comment 14 arad 2026-02-25 07:49:50 UTC
(In reply to Douglas Bagnall from comment #13)
Douglas, would you consider maybe keeping the SCEP http CA request, then validating it against the CA Samba got from LDAP? This way, the change will be minimal, but moreover, it will provide a way actually validating that the CA is legit and not tampered with, which will increase the overall security of samba and will be aligned with RFC8894 recommendation. It is true that the LDAP via GSSAPI is secured because of auth with Kerberos and singing the data but still the validation between the CA coming from 2 sources will add additional security layer.

Thanks
Comment 15 Douglas Bagnall 2026-02-25 19:00:57 UTC
(In reply to arad from comment #14)
> Douglas, would you consider maybe keeping the SCEP http CA request, 
> then validating it against the CA Samba got from LDAP?

I think it is best to drop http altogether. 

We know from bug 15557 (https://gitlab.com/samba-team/samba/-/merge_requests/3496) that in the real world people are using auto enrolment without NDES. This is why we fall back to LDAP if http is missing.

If an attacker can change the http response to alter the certificate, they can also change it to a 404 response, triggering the LDAP fallback. That makes the additional security look quite thin. All NDES offers is an opportunity to DoS via mismatch.

But also, what does it mean to be a domain member if you don't trust the DC?

We should keep it simple, and follow what people actually use. I am trying to find out if NDES without certs-in-LDAP is common, but there is no reason to insist people have both.
Comment 16 Douglas Bagnall 2026-02-27 03:49:43 UTC
Created attachment 18868 [details]
patch for 4.24
Comment 17 Douglas Bagnall 2026-02-27 03:50:42 UTC
Created attachment 18869 [details]
patch for 4.23
Comment 18 Douglas Bagnall 2026-02-27 03:51:29 UTC
Created attachment 18870 [details]
patch for 4.22
Comment 19 Douglas Bagnall 2026-02-27 03:54:04 UTC
Created attachment 18871 [details]
patch for 4.21

Patches for 4.21 to 4.24 are all the same, if I got things right.
Comment 20 Douglas Bagnall 2026-02-27 04:32:30 UTC
Created attachment 18872 [details]
draft advisory
Comment 21 Douglas Bagnall 2026-02-27 09:56:21 UTC
Created attachment 18873 [details]
advisory v2
Comment 22 Douglas Bagnall 2026-03-03 21:33:56 UTC
I note this sentence in commit message 2/4 needs an extra word:

> SCEP is for clients that are not domain members can access to certificates over LDAP.
Comment 23 Douglas Bagnall 2026-03-05 03:32:04 UTC
Advisory names 4.23.6, which is now out; should be 4.23.7.
Comment 24 Douglas Bagnall 2026-03-10 22:58:23 UTC
Created attachment 18884 [details]
patch for 4.24, 4.23, 4.22, 4.21 v2
Comment 25 Jennifer Sutton 2026-03-11 23:20:32 UTC
Comment on attachment 18884 [details]
patch for 4.24, 4.23, 4.22, 4.21 v2

> log.warn('Installing the server certificate only.')

Should this message say something like “…because NDES is not implemented”?

LGTM.
Comment 26 Douglas Bagnall 2026-03-11 23:35:41 UTC
(In reply to Jennifer Sutton from comment #25)
>> log.warn('Installing the server certificate only.')
>
> Should this message say something like “…because NDES is not implemented”?

I don't think so, because it isn't the true reason. 

The problem is we do things like this:

                 'cACertificate': get_string(base64.b64encode(es['cACertificate'][0]))

where we only grab [0], not [0..n].

But that is another bug that I don't want to fix in the security release.
Comment 27 Denis Cardon 2026-03-31 08:40:02 UTC
Hi Douglas,

Simon did raise the issue about the http call in January [1] but it seems that it was not deemed important...

There are GPO that are dedicated to deploy certificates (where the CA are store in the sysvol which storage and access is already secured), so I am wondering why we should do it here in cepces. Or one can get its CA from CN=Certificate Authorities in the LDAP, whose storage and access is also already secured.

So yes I think that having http downloads of the CA can be avoided in any cases.

Denis

[1] https://github.com/openSUSE/cepces/pull/80#issuecomment-3710625774
Comment 28 arad 2026-04-05 08:27:56 UTC
Hi, I would appreciate if you could confirm when the CVE is expected to be publicly released?. Many thanks.
Comment 29 Douglas Bagnall 2026-04-06 02:45:13 UTC
(In reply to arad from comment #28)
Release date is 2026-04-09.

A pre-announcement of that is going out very soon.
Comment 30 Björn Jacke 2026-04-08 10:48:17 UTC
Comment on attachment 18873 [details]
advisory v2

David, can you review+ here, please? Tomorrow is release date. Anybody else volunteering to review+ the advisory?
Comment 31 Björn Jacke 2026-04-08 16:35:32 UTC
the security release that was scheduled for tomorrow, will be postponed due to
new problems that have been identified with one of the fixes.

We will announce a new release date as soon as possible after the remaining
issues have been ruled out.
Comment 32 arad 2026-04-09 05:14:15 UTC
(In reply to Björn Jacke from comment #31)
Hi, is there any chance the CVE will be released in the coming days, such as tomorrow or within the next few days? Thanks!
Comment 33 David Mulder 2026-04-09 14:00:13 UTC
Comment on attachment 18873 [details]
advisory v2

>===========================================================
>== Subject:     auto-enrolment GPO installing CA certificate over http without verification
>==
>== CVE ID#:     CVE-2026-3012
>==
>== Versions:    all versions since 4.16
>==
>==
>== Summary:      To bootstrap a certificate chain a domain member must
>==               fetch a certificate without TLS. It was trusting HTTP
>==               for this when a more secure encrypted LDAP channel
>==               was also available.
>===========================================================
>
>===========
>Description
>===========
>
>If the certificate auto-enrollment GPO is enabled on domain members
>(both in Samba's smb.conf and using Windows GPME tool), a CA
>certificate may be fetched using a plain HTTP connection and installed
>in the member computer's trust store. This may give an attacker a
>chance to intercept the response, installing their chosen certificate
>instead.
>
>The URL from which the certificate is fetched follows a pattern used
>by Microsoft's Network Device Enrollment Service (NDES) to provide
>certificates to computers on the network that are not full domain
>members. Domain members should already have access to these
>certificates via better protected LDAP connections, so do not need the
>NDES link (Samba uses no other part of NDES).
>
>Pure Samba domains will not have auto-enrolment available, either
>through LDAP or HTTP, as Samba does not currently implement Active
>Directory Certificate Services. However, members of these domains are
>still vulnerable if the GPO is enabled.
>
>The patch removes the attempt to download the certificate and relies
>on the LDAP values.
>
>==================
>Patch Availability
>==================
>
>Patches addressing this issue have been posted to:
>
>    https://www.samba.org/samba/security/
>
>Additionally, Samba 4.21.11, 4.22.9, and 4.23.6 have been issued
>as security releases to correct the defect.  Samba administrators are
>advised to upgrade to these releases or apply the patch as soon
>as possible.
>
>==================
>CVSSv3 calculation
>==================
>
>CVSS3.1/AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N (8.0)
>
>==========
>Workaround
>==========
>
>If you do not enable certificate auto-enrolment using the Windows GPME
>tool, the vulnerable code will not run.
>
>If your smb.conf does not contain a line like 'apply group policies =
>yes', group policy will not be enabled, and the vulnerable code will
>not run (regardless of Windows GPME configuration).
>
>Intercepting the HTTP request requires some control over the local
>network or other devices to intercept or redirect traffic. Some
>network administrators might assess this as a low risk on their
>networks.
>
>=======
>Credits
>=======
>
>Reported by Arad Inbar, Nir Somech, and Ben Grinberg of the DREAM
>Security Research Team.
>
>Patches and this advisory provided by Douglas Bagnall of Catalyst and
>the Samba team.
>
>==========================================================
>== Our Code, Our Bugs, Our Responsibility.
>== The Samba Team
>==========================================================
Comment 34 David Mulder 2026-04-09 14:00:41 UTC
Comment on attachment 18884 [details]
patch for 4.24, 4.23, 4.22, 4.21 v2

>From 26a759cbcd67fb0fbed7d2efcfb64be21805a813 Mon Sep 17 00:00:00 2001
>From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
>Date: Fri, 27 Feb 2026 11:30:40 +1300
>Subject: [PATCH 1/4] CVE-2026-3012: gpo tests: fix test cleanup
>
>These tests are going to fail soon but as currently written they do
>not clean up after themselves, erroring instead of failing and causing
>cascading errors in subsequent tests. For now we don't care to make
>the other tests less fragile.
>
>BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003
>
>Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
>---
> python/samba/tests/gpo.py | 42 +++++++++++++++++++++++----------------
> 1 file changed, 25 insertions(+), 17 deletions(-)
>
>diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
>index 2e4696cd926..0972cd2f63c 100644
>--- a/python/samba/tests/gpo.py
>+++ b/python/samba/tests/gpo.py
>@@ -6951,6 +6951,7 @@ class GPOTests(tests.TestCase):
>         confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn
>         ca_cn = '%s-CA' % hostname.replace('.', '-')
>         certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn)
>+        self.addCleanup(ldb.delete, certa_dn)
>         ldb.add({'dn': certa_dn,
>                  'objectClass': 'certificationAuthority',
>                  'authorityRevocationList': ['XXX'],
>@@ -6959,6 +6960,7 @@ class GPOTests(tests.TestCase):
>                 })
>         # Write the dummy pKIEnrollmentService
>         enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
>+        self.addCleanup(ldb.delete, enroll_dn)
>         ldb.add({'dn': enroll_dn,
>                  'objectClass': 'pKIEnrollmentService',
>                  'cACertificate': dummy_certificate(),
>@@ -6967,6 +6969,7 @@ class GPOTests(tests.TestCase):
>                 })
>         # Write the dummy pKICertificateTemplate
>         template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn
>+        self.addCleanup(ldb.delete, template_dn)
>         ldb.add({'dn': template_dn,
>                  'objectClass': 'pKICertificateTemplate',
>                 })
>@@ -7012,11 +7015,6 @@ class GPOTests(tests.TestCase):
>             self.assertNotIn(b'Workstation', out,
>                              'Workstation certificate not removed')
> 
>-        # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
>-        ldb.delete(certa_dn)
>-        ldb.delete(enroll_dn)
>-        ldb.delete(template_dn)
>-
>         # Unstage the Registry.pol file
>         unstage_file(reg_pol)
> 
>@@ -7027,6 +7025,7 @@ class GPOTests(tests.TestCase):
>                                'MACHINE/REGISTRY.POL')
>         cache_dir = self.lp.get('cache directory')
>         store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
>+        self.addCleanup(store.log.close)
> 
>         machine_creds = Credentials()
>         machine_creds.guess(self.lp)
>@@ -7059,6 +7058,7 @@ class GPOTests(tests.TestCase):
>         confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn
>         ca_cn = '%s-CA' % hostname.replace('.', '-')
>         certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn)
>+        self.addCleanup(ldb.delete, certa_dn)
>         ldb.add({'dn': certa_dn,
>                  'objectClass': 'certificationAuthority',
>                  'authorityRevocationList': ['XXX'],
>@@ -7067,6 +7067,7 @@ class GPOTests(tests.TestCase):
>                 })
>         # Write the dummy pKIEnrollmentService
>         enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
>+        self.addCleanup(ldb.delete, enroll_dn)
>         ldb.add({'dn': enroll_dn,
>                  'objectClass': 'pKIEnrollmentService',
>                  'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
>@@ -7075,12 +7076,16 @@ class GPOTests(tests.TestCase):
>                 })
>         # Write the dummy pKICertificateTemplate
>         template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn
>+        self.addCleanup(ldb.delete, template_dn)
>         ldb.add({'dn': template_dn,
>                  'objectClass': 'pKICertificateTemplate',
>                 })
> 
>         with TemporaryDirectory() as dname:
>-            ext.process_group_policy([], gpos, dname, dname)
>+            try:
>+                ext.process_group_policy([], gpos, dname, dname)
>+            except Exception as e:
>+                self.fail(f"process_group_policy() raised {e}")
>             ca_crt = os.path.join(dname, '%s.crt' % ca_cn)
>             self.assertTrue(os.path.exists(ca_crt),
>                             'Root CA certificate was not requested')
>@@ -7169,11 +7174,6 @@ class GPOTests(tests.TestCase):
>             self.assertNotIn(b'Workstation', out,
>                              'Workstation certificate not removed')
> 
>-        # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
>-        ldb.delete(certa_dn)
>-        ldb.delete(enroll_dn)
>-        ldb.delete(template_dn)
>-
>         # Unstage the Registry.pol file
>         unstage_file(reg_pol)
> 
>@@ -7626,6 +7626,7 @@ class GPOTests(tests.TestCase):
>                                'MACHINE/REGISTRY.POL')
>         cache_dir = self.lp.get('cache directory')
>         store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb'))
>+        self.addCleanup(store.log.close)
> 
>         machine_creds = Credentials()
>         machine_creds.guess(self.lp)
>@@ -7667,6 +7668,8 @@ class GPOTests(tests.TestCase):
>         confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn
>         ca_cn = '%s-CA' % hostname.replace('.', '-')
>         certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn)
>+        self.addCleanup(ldb.delete, certa_dn)
>+
>         ldb.add({'dn': certa_dn,
>                  'objectClass': 'certificationAuthority',
>                  'authorityRevocationList': ['XXX'],
>@@ -7675,6 +7678,7 @@ class GPOTests(tests.TestCase):
>                 })
>         # Write the dummy pKIEnrollmentService
>         enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn)
>+        self.addCleanup(ldb.delete, enroll_dn)
>         ldb.add({'dn': enroll_dn,
>                  'objectClass': 'pKIEnrollmentService',
>                  'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
>@@ -7683,12 +7687,21 @@ class GPOTests(tests.TestCase):
>                 })
>         # Write the dummy pKICertificateTemplate
>         template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn
>+        try:
>+            ldb.delete(template_dn)
>+        except _ldb.LdbError:
>+            pass
>+
>+        self.addCleanup(ldb.delete, template_dn)
>         ldb.add({'dn': template_dn,
>                  'objectClass': 'pKICertificateTemplate',
>                 })
> 
>         with TemporaryDirectory() as dname:
>-            ext.process_group_policy([], gpos, dname, dname)
>+            try:
>+                ext.process_group_policy([], gpos, dname, dname)
>+            except Exception as e:
>+                self.fail(f"process_group_policy() raised {e}")
>             ca_list = [ca_cn, 'example0-com-CA', 'example1-com-CA',
>                        'example2-com-CA']
>             for ca in ca_list:
>@@ -7751,11 +7764,6 @@ class GPOTests(tests.TestCase):
>             self.assertNotIn(b'Workstation', out,
>                              'Workstation certificate not removed')
> 
>-        # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate
>-        ldb.delete(certa_dn)
>-        ldb.delete(enroll_dn)
>-        ldb.delete(template_dn)
>-
>         # Unstage the Registry.pol file
>         unstage_file(reg_pol)
> 
>-- 
>2.43.0
>
>
>From 85f26838744e29d477d8940c1ca27e08bd8dc4b3 Mon Sep 17 00:00:00 2001
>From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
>Date: Mon, 23 Feb 2026 11:01:57 +1300
>Subject: [PATCH 2/4] CVE-2026-3012: do not fetch certificate over http
>
>In the case where a certificate was found via HTTP, it was trusted
>without verification and put in the global CA store.
>
>There is no means to check the certificate other than by comparing it
>to certificates we may have gathered via LDAP, but in that case there
>is no advantage over just using the LDAP-derived certificates.
>
>Using the LDAP certificates was already the fallback case if HTTP
>failed, so we just make it the default.
>
>The HTTP fetch depends on the NDES service, which is a variant of
>Simple Certificate Enrolment Protocol (SCEP, RFC8894), but in fact
>Samba implements none of that protocol other than the HTTP fetch. SCEP
>is for clients that are not true domain members. Domain members can
>access to certificates over LDAP. This patch is not reducing SCEP
>client support because Samba never had it.
>
>BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003
>
>Reported-by: Arad Inbar, DREAM Security Research Team
>Reported-by: Nir Somech, DREAM Security Research Team
>Reported-by: Ben Grinberg, DREAM Security Research Team
>
>Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
>---
> python/samba/gp/gp_cert_auto_enroll_ext.py | 54 ++++------------------
> selftest/knownfail.d/gpo-auto-enrol        |  2 +
> 2 files changed, 11 insertions(+), 45 deletions(-)
> create mode 100644 selftest/knownfail.d/gpo-auto-enrol
>
>diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
>index 877659b043e..815436e11e9 100644
>--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
>+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
>@@ -16,7 +16,6 @@
> 
> import os
> import operator
>-import requests
> from samba.gp.gpclass import gp_pol_ext, gp_applier, GPOSTATE
> from samba import Ldb
> from samba.dcerpc import misc
>@@ -195,58 +194,24 @@ def get_supported_templates(server):
>     return out.strip().split()
> 
> 
>-def getca(ca, url, trust_dir):
>-    """Fetch Certificate Chain from the CA."""
>+def getca(ca, trust_dir):
>+    """Fetch a certificate from LDAP."""
>     root_cert = os.path.join(trust_dir, '%s.crt' % ca['name'])
>     root_certs = []
>-
>-    try:
>-        r = requests.get(url=url, params={'operation': 'GetCACert',
>-                                          'message': 'CAIdentifier'})
>-    except requests.exceptions.ConnectionError:
>-        log.warn('Could not connect to Network Device Enrollment Service.')
>-        r = None
>-    if r is None or r.content == b'' or r.headers['Content-Type'] == 'text/html':
>-        log.warn('Unable to fetch root certificates (requires NDES).')
>-        if 'cACertificate' in ca:
>-            log.warn('Installing the server certificate only.')
>-            der_certificate = base64.b64decode(ca['cACertificate'])
>-            try:
>-                cert = load_der_x509_certificate(der_certificate)
>-            except TypeError:
>-                cert = load_der_x509_certificate(der_certificate,
>-                                                 default_backend())
>-            cert_data = cert.public_bytes(Encoding.PEM)
>-            with open(root_cert, 'wb') as w:
>-                w.write(cert_data)
>-            root_certs.append(root_cert)
>-        return root_certs
>-
>-    if r.headers['Content-Type'] == 'application/x-x509-ca-cert':
>-        # Older versions of load_der_x509_certificate require a backend param
>+    if 'cACertificate' in ca:
>+        log.warn('Installing the server certificate only.')
>+        der_certificate = base64.b64decode(ca['cACertificate'])
>         try:
>-            cert = load_der_x509_certificate(r.content)
>+            cert = load_der_x509_certificate(der_certificate)
>         except TypeError:
>-            cert = load_der_x509_certificate(r.content, default_backend())
>+            cert = load_der_x509_certificate(der_certificate,
>+                                             default_backend())
>         cert_data = cert.public_bytes(Encoding.PEM)
>         with open(root_cert, 'wb') as w:
>             w.write(cert_data)
>         root_certs.append(root_cert)
>-    elif r.headers['Content-Type'] == 'application/x-x509-ca-ra-cert':
>-        certs = load_der_pkcs7_certificates(r.content)
>-        for i in range(0, len(certs)):
>-            cert = certs[i].public_bytes(Encoding.PEM)
>-            filename, extension = root_cert.rsplit('.', 1)
>-            dest = '%s.%d.%s' % (filename, i, extension)
>-            with open(dest, 'wb') as w:
>-                w.write(cert)
>-            root_certs.append(dest)
>-    else:
>-        log.warn('getca: Wrong (or missing) MIME content type')
>-
>     return root_certs
> 
>-
> def find_global_trust_dir():
>     """Return the global trust dir using known paths from various Linux distros."""
>     for trust_dir in global_trust_dirs:
>@@ -266,11 +231,10 @@ def changed(new_data, old_data):
> def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
>     """Install the root certificate chain."""
>     data = dict({'files': [], 'templates': []}, **ca)
>-    url = 'http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?' % ca['hostname']
> 
>     log.info("Try to get root or server certificates")
> 
>-    root_certs = getca(ca, url, trust_dir)
>+    root_certs = getca(ca, trust_dir)
>     data['files'].extend(root_certs)
>     global_trust_dir = find_global_trust_dir()
>     for src in root_certs:
>diff --git a/selftest/knownfail.d/gpo-auto-enrol b/selftest/knownfail.d/gpo-auto-enrol
>new file mode 100644
>index 00000000000..4bf4b8e3c72
>--- /dev/null
>+++ b/selftest/knownfail.d/gpo-auto-enrol
>@@ -0,0 +1,2 @@
>+^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\)
>+^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\)
>-- 
>2.43.0
>
>
>From 9c4aba0750dfd03eba8072c2f707f6fabc42cb74 Mon Sep 17 00:00:00 2001
>From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
>Date: Thu, 26 Feb 2026 14:21:01 +1300
>Subject: [PATCH 3/4] CVE-2026-3012: gp_auto_enrol: skip CAs not found in LDAP
>
>If a certificate is mentioned in a GPO but is not present as a
>cACertificate attribute on a pKIEnrollmentService object, we have no way
>of obtaining it, so we might as well forget it.
>
>BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003
>
>Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
>---
> python/samba/gp/gp_cert_auto_enroll_ext.py | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
>diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py
>index 815436e11e9..de8b310afd9 100644
>--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
>+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
>@@ -452,11 +452,21 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier):
>                     # This is a basic configuration.
>                     cas = fetch_certification_authorities(ldb)
>                     for _ca in cas:
>+                        if 'cACertificate' not in _ca:
>+                            log.warning(f"ignoring CA '{_ca['name']}' with no "
>+                                        "cACertificate in LDAP.")
>+                            continue
>+
>                         self.apply(guid, _ca, cert_enroll, _ca, ldb, trust_dir,
>                                    private_dir)
>                         ca_names.append(_ca['name'])
>                 # If EndPoint.URI starts with "HTTPS//":
>                 elif ca['URL'].lower().startswith('https://'):
>+                    if 'cACertificate' not in ca:
>+                        log.warning(f"ignoring CA '{ca['name']}' "
>+                                    f"({ca['URL']}) with no "
>+                                    "cACertificate in LDAP.")
>+                        continue
>                     self.apply(guid, ca, cert_enroll, ca, ldb, trust_dir,
>                                private_dir, auth=ca['auth'])
>                     ca_names.append(ca['name'])
>-- 
>2.43.0
>
>
>From 1f55154fbc3f207b22715ae8d492e135976c7f86 Mon Sep 17 00:00:00 2001
>From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
>Date: Fri, 27 Feb 2026 14:46:04 +1300
>Subject: [PATCH 4/4] CVE-2026-3012: gpo tests should use real certificates
>
>Or at least, more real than a short arbitrary byte string, so that
>the certificates can be parsed.
>
>This shows that certificate enrolment works via LDAP in the situations
>where we would have fetched them via HTTP.
>
>This does not fix the advanced_gp_cert_auto_enroll_ext test which
>wants to install certificates it has no access too. This will not be
>fixed in the security release.
>
>BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003
>
>Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
>---
> python/samba/tests/gpo.py           | 8 ++++----
> selftest/knownfail.d/gpo-auto-enrol | 1 -
> 2 files changed, 4 insertions(+), 5 deletions(-)
>
>diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py
>index 0972cd2f63c..5bdee29b50a 100644
>--- a/python/samba/tests/gpo.py
>+++ b/python/samba/tests/gpo.py
>@@ -7062,7 +7062,7 @@ class GPOTests(tests.TestCase):
>         ldb.add({'dn': certa_dn,
>                  'objectClass': 'certificationAuthority',
>                  'authorityRevocationList': ['XXX'],
>-                 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
>+                 'cACertificate': dummy_certificate(),
>                  'certificateRevocationList': ['XXX'],
>                 })
>         # Write the dummy pKIEnrollmentService
>@@ -7070,7 +7070,7 @@ class GPOTests(tests.TestCase):
>         self.addCleanup(ldb.delete, enroll_dn)
>         ldb.add({'dn': enroll_dn,
>                  'objectClass': 'pKIEnrollmentService',
>-                 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
>+                 'cACertificate': dummy_certificate(),
>                  'certificateTemplates': ['Machine'],
>                  'dNSHostName': hostname,
>                 })
>@@ -7673,7 +7673,7 @@ class GPOTests(tests.TestCase):
>         ldb.add({'dn': certa_dn,
>                  'objectClass': 'certificationAuthority',
>                  'authorityRevocationList': ['XXX'],
>-                 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
>+                 'cACertificate': dummy_certificate(),
>                  'certificateRevocationList': ['XXX'],
>                 })
>         # Write the dummy pKIEnrollmentService
>@@ -7681,7 +7681,7 @@ class GPOTests(tests.TestCase):
>         self.addCleanup(ldb.delete, enroll_dn)
>         ldb.add({'dn': enroll_dn,
>                  'objectClass': 'pKIEnrollmentService',
>-                 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I',
>+                 'cACertificate': dummy_certificate(),
>                  'certificateTemplates': ['Machine'],
>                  'dNSHostName': hostname,
>                 })
>diff --git a/selftest/knownfail.d/gpo-auto-enrol b/selftest/knownfail.d/gpo-auto-enrol
>index 4bf4b8e3c72..4b787a5ac86 100644
>--- a/selftest/knownfail.d/gpo-auto-enrol
>+++ b/selftest/knownfail.d/gpo-auto-enrol
>@@ -1,2 +1 @@
> ^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\)
>-^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\)
>-- 
>2.43.0
>
Comment 35 David Mulder 2026-04-09 14:02:11 UTC
Sorry, I don't know why bugzilla dumped the patch contents when I added a review+
Comment 36 Björn Jacke 2026-04-09 15:08:00 UTC
(In reply to arad from comment #32)
This is not possible within the next few days unfortunately. After the issue is sorted out, we'll need to set a date early enough for vendors to be prepared again.
Comment 37 arad 2026-04-09 17:11:16 UTC
(In reply to Björn Jacke from comment #36)
Thanks! Do you have an approximate estimated date?
Comment 38 Douglas Bagnall 2026-04-09 21:42:47 UTC
Adding CC Simon Fonteneau who independently discovered this.

See https://github.com/openSUSE/cepces/pull/80#issuecomment-3710625774 as per comment 27 here.
Comment 39 Björn Jacke 2026-05-15 13:01:19 UTC
Scheduled release date is now 2026-05-26.
Comment 40 Stefan Metzmacher 2026-05-21 16:16:41 UTC
Created attachment 18990 [details]
advisory v3

This updates the Credits section

Originally reported by:
- Arad Inbar of the DREAM Security Research Team
- Nir Somech of the DREAM Security Research Team
- Ben Grinberg of the DREAM Security Research Team
- Michalis Vasileiadis
Comment 41 Stefan Metzmacher 2026-05-26 09:16:00 UTC
I plan to upload the releases in about 3 hours from now...
Comment 42 Samba QA Contact 2026-05-26 12:35:40 UTC
This bug was referenced in samba v4-24-stable (Release samba-4.24.3):

ee7641e039952d9c0f5dab7dab8072e9c957e9b8
9a1765864ac1982e2a5f7e18b906e0bdb7cd4654
e8e36e3537cd75b6ab2dcddce2b6296e053bbd8e
be49a2233848b19fd133e847eaddec6b13facd06
Comment 43 Samba QA Contact 2026-05-26 12:36:20 UTC
This bug was referenced in samba v4-23-stable (Release samba-4.23.8):

f040d9a3e30c00fd74e15f284ce5fc0ec6035303
d77bf593c7e106ce9deefb92a424dc2930814b76
2b9d1982f9f9c6b418fa2c5fc44032463ed5f578
2d8f4ac98d9768af12409948449b9804282cf8ec
Comment 44 Samba QA Contact 2026-05-26 12:36:56 UTC
This bug was referenced in samba v4-22-stable (Release samba-4.22.10):

2ea796104ce1e39becaab59608ade4d30cc5352e
f21f87e0f64dc4aea8c9537f182282077ae6a37b
7337d99ed55c01cd5837029485cd971a48f761c2
6bde7ce351af70c017bb57fb02651ad57403188c
Comment 45 Samba QA Contact 2026-05-26 12:38:52 UTC
This bug was referenced in samba v4-24-test (Release samba-4.24.3):

ee7641e039952d9c0f5dab7dab8072e9c957e9b8
9a1765864ac1982e2a5f7e18b906e0bdb7cd4654
e8e36e3537cd75b6ab2dcddce2b6296e053bbd8e
be49a2233848b19fd133e847eaddec6b13facd06
Comment 46 Samba QA Contact 2026-05-26 12:39:42 UTC
This bug was referenced in samba v4-23-test (Release samba-4.23.8):

f040d9a3e30c00fd74e15f284ce5fc0ec6035303
d77bf593c7e106ce9deefb92a424dc2930814b76
2b9d1982f9f9c6b418fa2c5fc44032463ed5f578
2d8f4ac98d9768af12409948449b9804282cf8ec
Comment 47 Samba QA Contact 2026-05-26 12:40:12 UTC
This bug was referenced in samba v4-22-test (Release samba-4.22.10):

2ea796104ce1e39becaab59608ade4d30cc5352e
f21f87e0f64dc4aea8c9537f182282077ae6a37b
7337d99ed55c01cd5837029485cd971a48f761c2
6bde7ce351af70c017bb57fb02651ad57403188c
Comment 48 Samba QA Contact 2026-05-26 13:55:21 UTC
This bug was referenced in samba master:

c03e7dcf5113c30c4466e5cf902f69d1c09164d0
4c2db6489be1364a8ce2841f7eedcd976fa1463b
160e8319330858a3b792a8850440ce892fc585fc
935e5a9ecd38872454f24a9ab8ebb38744a7e2f9